The Design and Operation of a Multithread VHDL Testbench
The use of multithread architecture along with other testing concepts such as self-checking and behavioral level modeling can provide a high level of testing sophistication and flexibility.
By Mike Francois
Upcoming verification tools, such as Vera and Specman, use high-level object-oriented hardware verification languages (HVL) to simulate Verilog and VHDL designs. They allow for the development of flexible, reusable tests and will no doubt, improve the verification process for many companies in many situations.
However, as with most EDA tools, they are not a one-size-fits-all solution. There are financial costs (licenses) and schedule costs (learning curve) that must be absorbed to make these
tools part of a company's design and verification process.
Given these facts, the current capabilities of VHDL, and the promise of new object-oriented language extensions, there will continue to be justification for the creation of VHDL testbenchs to get the simulation job done.
The testbench architecture described here was used to verify the Flight Management Peripheral I/O (FMPIO) ASIC designed by Smiths Industries Aerospace. This ASIC provides multiple support functions for a Motorola MC68040
processor, including multiple DMA, UART, serial ARINC channels, dynamic bus sizing, timers, and error detection/correction (EDC). The ASIC contains 120,000 random gates, 9 memories, and an embedded microprocessor. This ASIC was completed with first pass silicon.
Multithread testbench defined
Multithread testbench is defined as a VHDL testbench that allows the operation of multiple concurrent processes, each running a separate simulation thread while gracefully sharing the resource of the VHDL design
under test.
Although this definition provides a concise academic definition of a multithread testbench, an understanding of its two basic tenets, multiple concurrent processes and resource sharing, require additional explanation.
The concept of multiple concurrent processes is well known to VHDL designers. It is one of the unique language features that allow VHDL to model the parallel nature of circuit operation. Extending this idea to a VHDL testbench is not difficult, but is best illustrated
through the use of a simple example.
Figure 1 Two-process VHDL device
Figure 1 represents a VHDL device that contains two concurrent processes. Each of these processes is a self-contained block of VHDL code that performs a transformation function between the input and output signals. The output of each process is dependent only on its corresponding input and is unaffected by any other stimulus in the design.
Figure 2 Two-process
VHDL device with testbench
To validate the correct operation of a VHDL device various stimuli must be applied to the inputs while the device outputs are monitored. As shown in Figure 2 the generation of input stimuli is the responsibility of the VHDL testbench.
This testbench contains a series of VHDL assignment and delay statements within a single process or simulation thread. These statements are performed sequentially starting at the top of the process and generate both timing and functional
stimuli to the VHDL device inputs.
This single-thread testbench is basic and easy to code. But because it drives both input signals from a single block of code, it requires careful arrangement of assignment and delay statements to generate the input stimulus. This style of testbench although simple does not normally lend itself to the testing of complex VHDL devices.
Figure 3 Two-process VHDL device with two-process testbench
Replacing the testbench with one that
supports multiple concurrent processes is presented in Figure 3. The assignment and delay statements are now divided between two VHDL code blocks (or processes) and constructed in such a way that the same input timing and functional stimulus are provided. The complexity and shortcomings imposed by the single-thread, sequential code are overcome and the operation of each signal is more obvious and straightforward. This in turn enhances maintainability, reuse, and flexibility of the testbench. As the
complexity of the VHDL device increases, the benefit of a separate, concurrent process becomes more obvious.
During the testing of the FMPIO ASIC, as many as 30 individual concurrent testbench processes were operating at the same time. Each of these processes handled some specific function of the overall simulation effort, such as a serial channel or DMA operation. All of the processes were written in a modular, self-contained fashion so that they could easily be mixed and matched depending on the testing
requirements. This mixing allowed for over 70 unique test driver combinations to be created for the verification of the FMPIO ASIC.
Figure 4 FMPIO ASIC concurrent process block diagram
The concept and implementation of multiple independent processes within a testbench is a relatively simple procedure. However it creates a new, and arguably more difficult problem, namely that of VHDL device resource sharing. This then is the other leg of multithread operation.
A VHDL
shared resource can be as simple as an input discrete or as complex as a multibus system backplane. What a shared resource is and how the various processes use it depends on the application of the VHDL device under test. In general, VHDL shared resources tend to be signals at the interface of the design that need to be accessed by more than one testbench process. A shared resource common to many designs (as shown in Figure 4) is the processor or system interface bus.
The implementation of resource
sharing within a testbench requires communication and arbitration between the various independent simulation processes. Support for these operations is a standard feature of object-oriented software languages and will hopefully be adopted as part of the VHDL language in the near future. However, by using the language as it exists today with creative testbench coding, support for shared VHDL resources can be provided.
Resource sharing of the system interface bus
The primary resource that required
sharing during the FMPIO ASIC testing was the system interface bus. This resource was comprised of a 32-bit address bus, a 64-bit data bus, and more than a dozen control signals. This resource was heavily used and shared among the 30 testbench processes to allow access to the internal registers within the ASIC.
To ensure test flexibility, no restrictions were placed on when or for what purpose the interface bus could be requested by any of the simulation processes. Since each process operated independently
of all others, this flexibility led to multiple conflicting requests for the bus during each simulation. The problem of resource sharing was solved by the application of a VHDL resolution function on a resolved signal.
Because a resolved signal is allowed to have multiple drive sources, it lent itself well to the problem of multiple requesting processes. A pair of resolved signals of type natural called
Request
and
Grant
were connected as shown in Figure 5, between the interface bus
resource and each of the simulation processes. Signal
Request
was driven from multiple simulation processes to the bus resource, while
Grant
was driven out of the resource to the processes. Since these two signals have a unique purpose that needed to be duplicated for every resource in the design, they were formed into a VHDL record named a
Resource Channel.
To drive the
Request
signal each simulation process was assigned a unique priority value by the test developer. The value
was based on the relative importance of the simulation operation performed by each process (i.e. higher priority tasks were assigned higher priority request values). As each of these values was driven during bus resource requests; arbitration or signal resolution was performed to select the highest priority process needing service.
Figure 5 Process resource sharing
Once the interface bus resource receives the request value, it provides usage rights to the high
priority process by assigning the request value onto the
Grant
signal. Each of the requesting processes has been held in a wait state watching for activity on the
Grant
signal. Once this occurs and the process verifies that the grant value matches its request, it proceeds with the remainder of the bus operation.
The code used to suspend each of the processes can be simple or complex depending on which features are needed. The rudiments of the code are formed from a VHDL
while
loop:
ResourceChannel.Request
<
= Priority; --Request resource.
wait for
0 ns;
while
ResourceChannel.Grant /= Priority
loop
--Wait for resource grant.
wait on
ResourceChannel.Grant;
end loop
;
Depending on the function of each simulation, it is possible that one or more requesting processes will be blocked for long periods of time. If this is not an acceptable side effect, a simple change to the above code will ensure that the
longer a process has requested a resource, the higher priority it is assigned.
ResourceChannel.Request
<
= Priority; --Request resource.
wait for
0 ns;
while
ResourceChannel.Grant /= Priority
loop
--Wait for resource grant.
wait on
ResourceChannel.Grant;
Priority := Priority + 10; --Increase req pri value;
ResourceChannel.Request
<
= Priority;
end loop
;
Once a process has been granted access to the bus,
data and address required for the bus operation are passed to the resource by way of shared variables defined in the VHDL testbench package. To ensure that the granted process has properly registered the data, the resource handler code must wait for a small but "real" amount of simulation time. All resource handlers within the FMPIO ASIC testbench waited for 1 ns.
The handler reads the shared variables and performs the bus operation. After completion, the resource removes the priority value from the
Grant
signal, informing all processes that the action has finished and that the resource arbitration cycle will begin again. The requesting process that has just received service now removes its request and continues on with its next simulation task.
A condition that should be carefully avoided is allowing two or more processes to use the same priority value. Doing this can result in a resource grant being awarded to multiple processes that will lead to indeterminate simulation results.
VHDL
multithread code example
VHDL code for implementing a shared resource and its operation is provided in the following example. Some portions of the code and comments have been condensed or removed.
VHDL Testbench Package
The VHDL testbench package defines several of the required components needed to properly use a shared resource. The resource resolution function (
ResolvedPriority
) is defined and coded here as are procedure calls (
AsicWrite
) used by the test driver file.
Additionally, any shared variables required for data passing need to be declared at this level of abstraction.
Package
package
test_pkg
is
-------------------
type
NaturalArray
is array( positive range
<
> )
of natural;
function
ResolvedPriority( ArrayValues_v : NaturalArray )
return natural
;
-------------------------------
--Define the Resource Channel that combines both the Request
--and Grant signal into a single
record to allow less complex
--port definitions.
--------------------------------
type
ResourceChannel
is record
Request : ResolvedPriority
natural;
Grant : ResolvedPriority
natural;
end record
ResourceChannel;
---------------------------------
--AsicWrite is called by the simulation processes to gain
--access to the bus resource. This procedure is NOT the
--resource handler, but the caller TO the
handler.
----------------------------------
procedure
AsicWrite(
signal
Resource_s :
inout
ResourceChannel;
Priority_c :
in
natural
;
Add_c :
in
std_logic_vector( 7 downto 0 );
Data_c :
in
std_logic_vector( 7 downto 0 ));
-----------------------------------
--Define the shared variables that will be needed to pass
--data between the AsicWrite procedure and
the resource handler.
-----------------------------------
type
ProcessorBusRecord
is record
Add : std_logic_vector( 7 downto 0 );
Data : std_logic_vector( 7 downto 0 );
end record
ProcessorBusRecord;
shared variable
PBus_sv : ProcessorBusRecord;
end package
test_pkg;
-----------------------------------
Package Body
package body
test_pkg
is
------------------
--Resolution function that selects the highest value natural
--out of an array of naturals. This array of naturals comes
--from the multi simulation processes driving their priority
--values onto the Request signal.
------------------------------------
function
ResolvedPriority( ArrayValues_v : NaturalArray )
return natural is
------------------------------------
variable
RetValue_v :
natural
:= 0;
begin
for
Index
in
ArrayValues_v'range
loop
if
( ArrayValues_v( Index ) > RetValue_v )
then
RetValue_v := ArrayValues_v( Index );
end if
;
end loop
;
return
RetValue_v;
end function
ResolvedPriority;
------------------------------------
------------------------------------
--AsicWrite is called by the simulation processes to gain
--access to the bus resource. This procedure is NOT the
--resource handler, but the caller TO the handler.
-------------------------------------
procedure
AsicWrite(
signal
Resource_s :
inout
ResourceChannel;
Priority_c :
in natural;
Add_c :
in
std_logic_vector( 7 downto 0 );
Data_c :
in
std_logic_vector( 7 downto 0 )) is
variable
Priority_v :
natural
;
begin
Priority_v := Priority_c;
Resource_s.Request
<
= Priority_v;
wait for
0 ns;
while
Resource_s.Grant /= Priority_v
loop
wait on
Resource_s.Grant;
end loop
;
--------------------------------------
--Place bus cycle data into shared variables for
--access by the resource handler.
--------------------------------------
PBus_sv.Add := Add_c;
PBus_sv.Data := Data_c;
--Wait until bus
cycle is over.
wait un
til Resource_s.Grant = 0;
--Release the request for the bus.
Resource_s.Request
<
= 0;
wait for
0 ns;
end procedure
AsicWrite;
end package body
test_pkg;
VHDL Testbench
The testbench code contains the actual resource process (
ProcessorBusDriver
) that communicates with the VHDL design under test. There are, however, no restrictions on placing this code in a component within the testbench,
or in any other file. The only requirement is that the test driver processes and the resource have visibility to the same "resource channel" record.
The timing contained in the
ProcessorBusDriver
process is arbitrary and has been provided only to facilitate this example.
Testbench Entity
entity
TestBench
isend entity
TestBench;
Testbench Architecture
architecture
TestBenchArch
of
TestBench
is
--------------------------------
--Call
out design under test
--------------------------------
component
Asic
port
( Add_p :
in
std_logic_vector( 7 downto 0 );
Data_p :
in
std_logic_vector( 7 downto 0 ));
end component
;
-------------------------------
--Call out test driver file
-------------------------------
component
TestDriver
port
( PBus_p :
inout
ResourceChannel );
end component
;
signal
PBus_s : ResourceChannel;
signal
Add_s : std_logic_vector( 7 downto 0 ) := "ZZZZZZZZ";
signal
Data_s : std_logic_vector( 7 downto 0 ) := "ZZZZZZZZ";
begin
TB1: TestDriver
port map
( PBus_p => PBus_s );
U1 : Asic
port map
(Add_p => Add_s, Data_p => Data_s );
ProcessorBusDriver :
process is
--------------------------------
begin
--------------------------------
--Wait for a positive value on the
Resource.Request
--signal before proceeding. A positive value
--indicates that a simulation process is requesting
--this resource. Only the highest value will appear
--on the Request signal due to the resolution
--function.
--------------------------------
if
( PBus_s.Request = 0 )
then
wait on
PBus_s.Request;
end if
;
---------------------------------
--Acknowledge the Request signal with the Grant
--signal.
---------------------------------
PBus_s.Grant
<
= PBus_s.Request;
---------------------------------
--Wait a short amount of time for the grant to be
--registered with the simulation process and for
--the process to load the shared variable data.
---------------------------------
wait for
1 ns;
---------------------------------
--Get the data for the resource operation from the
--shared variable data record and perform the
bus
--operation.
----------------------------------
Add_s
<
= PBus_sv.Add;
Data_s
<
= PBus_sv.Data;
wait for
5 ns;
Add_s
<
= "ZZZZZZZZ";
Data_s
<
= "ZZZZZZZZ";
----------------------------------
--Cycle has ended, release grant, wait and return
--to the beginning of this process to arbitrate
--again.
----------------------------------
PBus_s.Grant
<
= 0;
wait for
5 ns;
end process
ProcessorBusDriver;
end
TestBenchArch;
VHDL Test Driver
The VHDL test driver file is where the multiple independent simulation processes reside. This example contains three processes but there is no restriction on this number beyond the programmer's ability to maintain the code.
In this example each of the three processes is requesting the same resource through the
AsicWrite
procedure shown above in the VHDL testbench package. The name of the resource being
called, the priority level, and any additional data needed to perform the operation are passed to the resource by the procedure call.
Test Driver Entity
entity
TestDriver
is
port
( PBus_p :
inout
ResourceChannel);
end entity
TestDriver;
Test Driver Architecture
architecture
TestName01
of
TestDriver
is
----------------------------
begin
----------------------------------
--Process A is assigned
a request priority value of 1.
-----------------------------------
ProcessA :
process is
---------------
begin
wait for
5 ns;
AsicWrite( PBus_p, 1, x"11", x"11" );
wait
; --Stall out process for example.
end process
ProcessA;
-------------------------------
--Process B is assigned a request priority value of 2.
-------------------------------
ProcessB :
process is
-------------------------------
begin
--------------
wait for
5 ns;
AsicWrite( PBus_p, 2, x"22", x"22" );
wait
; --Stall out process for example.
end process
ProcessB;
--------------------------------
--Process C is assigned a request priority value of 3.
--------------------------------
ProcessC :
process is
---------------
begin
wait for
8 ns;
AsicWrite( PBus_p, 3, x"33", x"33"
);
wait
; --Stall out process for example.
end process
ProcessC;
end
TestName01;
Simulation Output
The following figure shows a simulation run of the example code above.
Process A with a priority of 1 and process B with a priority of 2 both request the bus resource at 5ns. Process B, having the higher priority is granted first access to the bus. Data is passed to the resource handler and a bus cycle begins. Process B drops its
request at 15ns after the bus operation has been completed, but in the interim (at 8ns) process C having a priority of 3, has registered its request.
At 15ns when the second round of arbitration begins, process A and C are both waiting for the bus resource. C takes priority and performs its cycle first, followed by process A with its lower priority.
Figure 6 VHDL code example simulation
Development and operation of a multithread testbench is possible using the
VHDL language as it exists today. This style testbench provides flexibility and reuse that make it a reasonable alternative to the use of hardware verification language tools that require additional funding and training.